package com.shakespace.artofandroid.chapter02

import android.content.*
import android.net.Uri
import android.os.*
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.shakespace.artofandroid.*
import com.shakespace.artofandroid.chapter02.basic.Remote01Activity
import com.shakespace.artofandroid.chapter02.binderpool.BinderPool
import com.shakespace.artofandroid.chapter02.messenger.MessengerService
import com.shakespace.artofandroid.chapter02.remote.BookService
import com.shakespace.artofandroid.chapter02.socket.TCPService
import com.shakespace.artofandroid.global.Constants
import com.shakespace.firstlinecode.global.TAG
import com.shakespace.firstlinecode.global.start
import kotlinx.android.synthetic.main.activity_chapter2.*
import java.io.*
import java.lang.ref.WeakReference
import java.net.Socket
import java.text.SimpleDateFormat

class Chapter2Activity : AppCompatActivity() {

    //-----------------------
    private val handlerReplyMessenger = Messenger(ReplyMessengerHandler())

    class ReplyMessengerHandler : Handler(Looper.getMainLooper()) {

        private var activity: WeakReference<Chapter2Activity>? = null

        override fun handleMessage(msg: Message) {
            when (msg.what) {
                Constants.MSG_REPLY_TO_CLIENT -> {
                    Log.e(this.TAG, "Client handleMessage: ${msg.data["reply"]}")
                }
                Constants.MSG_ADD_NEW_BOOK -> {
                    Log.e(this.TAG, "handleMessage: ${msg.data["book"]}")
                }
                Constants.MSG_SOCKET_CONNECTED -> {
                    Log.e(this.TAG, "handleMessage: Socket Connected")
                    activity = WeakReference(msg.obj as Chapter2Activity)
                    activity?.get()?.apply {
                        tv_display.hint = "connected"
                        btn_send.isEnabled = true
                    }
                }
                Constants.MSG_SOCKET_NEW_MSG -> {
                    val msg = msg.obj as String
                    activity?.get()?.apply {
                        tv_display.text = "${tv_display.text} Service : ${format(System.currentTimeMillis())}  $msg \n"
                    }
                }
                else -> ""
            }
        }
    }

    // ------------ Messenger -------------
    private var iMsgService: Messenger? = null

    private val msgConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {

        }

        // 2020-01-09 NOTE: what message can carry only "what / arg1 / arg2 / Bundle / replyTo"
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.e(this.TAG, "onServiceConnected: msg service")
            // get the Messenger
            iMsgService = Messenger(service)
            val message = Message.obtain(null, Constants.MSG_FROM_CLIENT)
            val bundle = Bundle().also {
                it.putString("msg", "this is client")
            }
            message.data = bundle
            // 2020-01-09 NOTE: set replyTo , when service will reply msg to this handler
            message.replyTo = handlerReplyMessenger
            // send msg
            iMsgService?.send(message)
        }
    }

    // ---------- book ------------
    private val onNewBookAddedListener = object : IOnNewBookAddedListener.Stub() {
        override fun onNewBookAddedListener(newBook: Book?) {
            // use handler is enough , but we already has a Messenger  , so ...
            handlerReplyMessenger.send(Message.obtain(null, Constants.MSG_ADD_NEW_BOOK).also { msg ->
                msg.data = Bundle().also { it.putParcelable("book", newBook) }
            })
        }
    }


    private var iBookManager: IBookManager? = null

    private val connection = object : ServiceConnection {
        override fun onServiceDisconnected(p0: ComponentName?) {
            Log.e(this.TAG, "onServiceDisconnected: ")
        }

        override fun onServiceConnected(componentName: ComponentName?, binder: IBinder?) {
            Log.e(this.TAG, "onServiceConnected: ")
            iBookManager = IBookManager.Stub.asInterface(binder)

            // 2020-01-08 NOTE: when we get remote Service, link to Death  just like set a linstener  , when connection died ,
            iBookManager?.asBinder()?.linkToDeath(deathRecipient, 0)
//            iBookManager.asBinder().unlinkToDeath(deathRecipient, 0)

            iBookManager?.registerListener(onNewBookAddedListener)
        }
    }


    val deathRecipient = object : IBinder.DeathRecipient {
        override fun binderDied() {
            val binderAlive = iBookManager?.asBinder()?.isBinderAlive

            // call when binder died
            iBookManager?.asBinder()?.unlinkToDeath(this, 0)
            Log.e(this.TAG, "binderDied: binderAlive == $binderAlive , thread name =  ${Thread.currentThread().name}")
            iBookManager = null
        }
    }

    //---- SOCKET----
    private var socket: Socket? = null
    private var writer: PrintWriter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_chapter2)

        tv_one.setOnClickListener {
            start(Remote01Activity::class.java)
        }


        tv_two.setOnClickListener {
            val intent = Intent(this, BookService::class.java)

            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }

        tv_three.setOnClickListener {
            val id = (0..1000).random()
            val name = (id % 26 + 48).toChar().toString()
            iBookManager?.addBook(Book(id, name))
        }

        tv_four.setOnClickListener {
            Log.e(this.TAG, "onCreate: ${iBookManager?.bookList}")
        }

        tv_msg.setOnClickListener {
            val intent = Intent(this, MessengerService::class.java)

            bindService(intent, msgConnection, Context.BIND_AUTO_CREATE)
        }

        tv_provider.setOnClickListener {
            val uri = Uri.parse("content://com.shakespace.artofandroid.book.provider")
            contentResolver.query(uri, null, null, null, null, null)
            contentResolver.query(uri, null, null, null, null, null)
        }

        tv_test.setOnClickListener {
            val uri = Uri.parse("content://com.shakespace.artofandroid.book.provider/book")
            val id = (0..1000).random()
            val values = ContentValues().apply {
                put("name", "First $id")
                put("_id", id)
            }
            contentResolver.insert(uri, values)

            val cursor = contentResolver.query(uri, arrayOf("_id", "name"), null, null, null)
//            startManagingCursor(cursor)
            cursor?.apply {
                while (cursor.moveToNext()) {
                    val id = cursor.getInt(cursor.getColumnIndex("_id"))
                    val name = cursor.getString(cursor.getColumnIndex("name"))
                    Log.e(this.TAG, "book id = $id , book name = $name ")
                }
                close()
            }
        }
        tv_socket.setOnClickListener {

            startService(Intent(this, TCPService::class.java))

            Thread {
                while (socket == null) {
                    try {
                        socket = Socket("localhost", 8600)
                        // get writer
                        writer = PrintWriter(BufferedWriter(OutputStreamWriter(socket?.getOutputStream())), true)
                        handlerReplyMessenger.send(Message.obtain(null, Constants.MSG_SOCKET_CONNECTED).also {
                            it.obj = this
                        })
                    } catch (e: Exception) {
                        SystemClock.sleep(1000)
                        Log.e(this.TAG, "onCreate: connect tcp server failed  retry ....")
                    }
                }

                try {
                    // receive msg from service
                    val bufferedReader = BufferedReader(InputStreamReader(socket?.getInputStream()))
                    while (!this.isFinishing) {
                        val msg = bufferedReader.readLine()
                        Log.e(this.TAG, "onCreate:  reader  $msg")
                        if (msg != null) {
                            handlerReplyMessenger.send(Message.obtain(null, Constants.MSG_SOCKET_NEW_MSG).also {
                                it.obj = msg
                            })
                        }
                    }

                    writer?.close()
                    bufferedReader.close()
                    socket?.close()
                    btn_send.isEnabled = false
                } catch (e: Exception) {
                    Log.e(this.TAG, "onCreate: ${e.localizedMessage}")
                }

            }.start()
        }

        btn_send.setOnClickListener {
            val text = et_input.text.trim().toString()
            if (text.isNotEmpty() && writer != null) {
                // should be new Thread , or NetworkOnMainThreadException
                Thread {
                    // send to service
                    writer?.println(text)
                }.start()
                et_input.setText("")
                // display client msg
                tv_display.text = "${tv_display.text} Client : ${format(System.currentTimeMillis())}  $text \n"
            }
        }

        /**
         *
        https://stackoverflow.com/questions/39171469/how-do-i-access-a-java-static-method-on-a-kotlin-subclass
        In Kotlin, unlike Java, static members are not inherited by subclasses, even though they can be called inside a subclass without the base class name.

        Outside a subclass, you have to call the base class static functions using the base class name:

        WakefulBroadcastReceiver.completeWakefulIntent(intent)
        This behavior seems to lie within the companion objects concept: companion objects of classes in a hierarchy are not included into each other.
         */
        tv_binder_pool.setOnClickListener {
            Thread {
                val binderPool = BinderPool.getInstance(this)

                // 2020-01-14 NOTE: if service running in remote process, can't cast BinderProxy to ComputeImpl or SecurityCenterImpl
//                val compute = binderPool.queryBinder(Constants.BINDER_COMPUTE) as ICompute
//                val securityCenter = binderPool.queryBinder(Constants.BINDER_SECURITY_CENTER) as ISecurityCenter

                // 2020-01-14 NOTE: In multiple process , only can use asInterface , it will get proper obj by check process
                val securityCenterByAsInterface =
                    ISecurityCenter.Stub.asInterface(binderPool.queryBinder(Constants.BINDER_SECURITY_CENTER))
                val computeByAsInterface =
                    ICompute.Stub.asInterface(binderPool.queryBinder(Constants.BINDER_COMPUTE))

                try {
//                    val result1 = compute.add(2, 4)
//                    val encrypt1 = securityCenter.encrypt("this is a content")
//                    Log.e(this.TAG, "onCreate: $result1   --- $encrypt1")

                    val result2 = computeByAsInterface.add(2, 4)
                    val encrypt2 = securityCenterByAsInterface.encrypt("this is a content")
                    Log.e(this.TAG, "onCreate: $result2   --- $encrypt2")
                } catch (e: Exception) {
                    Log.e(this.TAG, "onCreate: ${e.localizedMessage}")
                }
            }.start()

        }

    }

    fun format(date: Long): String {
        val sdf = SimpleDateFormat("HH:mm:ss")
        return sdf.format(date)
    }


    override fun onDestroy() {
        iBookManager?.let {
            unbindService(connection)
            it.unregisterListener(onNewBookAddedListener)
        }
        iMsgService?.let {
            unbindService(msgConnection)
        }
        try {
            Log.e(this.TAG, "onDestroy: stop service")
            stopService(Intent(this, TCPService::class.java))
            socket?.shutdownInput()
            socket?.close()
        } catch (e: Exception) {
            Log.e(this.TAG, "onDestroy: ${e.localizedMessage}")
        }
        super.onDestroy()
    }
}
